/*
 The Plot drawing class

 Example:
 var p = new Plot($('plot'),  {
	data: sampleData,
	type: 'bars_double'
 });
*/

var Plot = new Class({
	Implements: [Options],

	initialize: function(canvas, options) {
		// Default options
		defaults = {
			data: [],                       // The data to display
			type: 'bars_single',            // The chart type: bars_single, bars_double, lines or pie
			guideCount: 6,                  // Tells how much guides should it display
			roundTo: 1,                     // How much should it round the values
			lineMarkers: true,              // Tells if it shoould display line markers
			style: ['#4dbce9','#26ade4'],   // The colors of the chart
			names: [],                      // Names of the data
			legend: true,                   // Tells if it should draw the legend
			table: false                    // Tells if it should show a table with data
		};

		// Setting the parameters
		canvas = $(canvas);
		var d = canvas.getStyles('width','height');
		this.width = d.width.toInt();
		this.height = d.height.toInt();
		this.drawWidth = this.width-26;
		this.drawHeight = this.height-60;
		this.offsets = canvas.getPosition();
		this.canvas = canvas.getContext('2d');
		this.kanwas = canvas;
		this.cid = (canvas.id) ? canvas.id+'_text' : 'plot_text';
		this.setOptions(defaults, options);
		this._round = (10).pow(this.options.roundTo);
		this.style = this.parseColors(this.options.style);

		// Injecting the captions container
		new Element('div', {'id':this.cid}).inject(canvas,'after');

		// Converting the data
		var a = this.options.data;
		if ($type(a)=='array') {
			var aa = new Hash();
			a.length.times(function(i){
				if ($type(a[i])=='number') a[i] = [a[i]];
				aa.set(i, a[i]);
			});
			a = aa;
		} else if ($type(a)=='object') a = $H(a);
		this.data = a;

		// Finally, drawing the chart
		this.draw();
	},
	draw: function() {
		// Shortening variables
		var c = this.canvas;
		var g = this.options.guideCount;
		var t = this.options.type,
		s = this.style;
		var v = this.data.getValues();
		var x = this;

		// Drawing the legend
		if (this.options.legend) this.drawLegend();

		// Showing the table
		if (this.options.table) this.showTable();

		// Drawing the background
		c.fillStyle = s.background;
		c.fillRect(26,20,this.drawWidth,this.drawHeight);

		// Drawing guides
		c.fillStyle = s.header;
		c.strokeStyle = s.border;
		c.lineJoin = 'round';
		c.lineCap = 'round';
		c.fillRect(26,0,this.drawWidth,20);
		c.strokeRect(26,0,this.drawWidth,this.height-40);
		drawLine.bind(c,[26,20,this.width,20])();

		if (!t.test(/^pie/)) {
			c.strokeStyle = s.guides;
			c.lineWidth = s.guideWidth;
			merged = [];
			v.length.times(function(j){merged.extend(v[j]);});
			if (t=='bars_double') {
				merged.empty();
				v.each(function(i){ merged.push(total(i)); });
			}
			this._max = max(merged, this.options.guideCount);

			var step = this.drawHeight/g;
			var cstep = this._max/g;
			g.times(function(i){
				newy = (i*step)+20;
				el = new Element('span', {
					'styles': {
						'color': s.titles,
						'font-size': '11px',
						'font-weight': 'bold',
						'font-family': 'Arial, Helvetica, sans-serif',
						'position': 'absolute',
						'left': x.offsets.x-2
					}
				}).set('text', Math.round((x._max-cstep*i)*x._round)/x._round).inject($(x.cid));
				el.setStyle('top', x.offsets.y+newy-el.getOffsetSize().y/2);

				if ((i!=0)&&(i<g)) drawLine.bind(c,[26,newy,x.width,newy])();
			});
		}
		c.lineWidth = s.lineWidth;

		// Drawing the plot based on the type
		switch(t) {
			case 'bars_single':
			default:
				this.drawBarsSingle(); break;
			case 'bars_double': this.drawBarsDouble(); break;
			case 'lines': this.drawLinePlot(); break;
			case 'pie': this.drawPie(); break;
			case 'pie_3d': this.drawPie3d(); break;
		}
	},
	// Plot type functions
	drawBarsSingle: function() {
		// Shortening variables
		var c = this.canvas;
		var a = this.data;
		var v = a.getValues();
		var k = this.style;
		var x = this;
		var m = this._max;

		// Calculating bar width
		var one = (this.drawWidth-(a.getKeys().length+1)*26)/a.getKeys().length;
		var every = one/v[0].length;

		// Drawing bars and caption
		a.getKeys().length.times(function(i){
			el = new Element('span', {
				'styles': {
					'font-size': '11px',
					'font-weight': 'bold',
					'font-family': 'Arial, Helvetica, sans-serif',
					'color': k.captions,
					'position': 'absolute',
					'top': x.offsets.y+3+'px'
				}
			}).set('text', a.getKeys()[i]).inject($(x.cid));
			el.setStyle('left', x.offsets.x-el.getOffsetSize().x/2+52+one/2+(one+26)*i);
			v[0].length.times(function(j){
				if (k.autoColor) {
					if ($type(k.data)=='array') fs = k.data[0];
					else fs = k.data;
					col = new Color(fs);
					sat = col.hsb[2]+j*5;
					if (sat>100) sat=100;
					c.fillStyle = col.setBrightness(sat).hsbToRgb().rgbToHex();
				} else c.fillStyle = k.data[j%k.data.length];
				c.strokeStyle = k.dataBorder;
				c.lineWidth = k.borderWidth;
				xxx = (k.stroke) ? 'both' : true;
				value = x.drawHeight*(v[i][j]/m);
				drawRectBot.bind(c, [52+(every*j)+((one+26)*i),x.height-40,every,value,xxx])();
			});
		});
	},
	drawBarsDouble: function() {
		// Shortening variables
		var c = this.canvas;
		var a = this.data;
		var v = a.getValues();
		var k = this.style;
		var x = this;
		var temp = [];
		v.each(function(i){ temp.push(total(i)); });
		var m = max(temp, this.options.guideCount);

		// Calculating bar width
		var one = (this.drawWidth-(a.getKeys().length+1)*26)/a.getKeys().length;

		// Drawing bars and captions
		a.getKeys().length.times(function(i){
			el = new Element('span', {
				'styles': {
					'font-size': '11px',
					'font-weight': 'bold',
					'font-family': 'Arial, Helvetica, sans-serif',
					'color': k.captions,
					'position': 'absolute',
					'top': x.offsets.y+3+'px'
				}
			}).set('text', a.getKeys()[i]).inject($(x.cid));
			el.setStyle('left', x.offsets.x-el.getOffsetSize().x/2+52+one/2+(one+26)*i);

			oldvalue = 0;
			v[0].length.times(function(j){
				if (k.autoColor) {
					if ($type(k.data)=='array') fs = k.data[0];
					else fs = k.data;
					col = new Color(fs);
					sat = col.hsb[2]+j*5;
					if (sat>100) sat=100;
					c.fillStyle = col.setBrightness(sat).hsbToRgb().rgbToHex();
				} else c.fillStyle = k.data[j%k.data.length];
				c.strokeStyle = k.dataBorder;
				c.lineWidth = k.borderWidth;
				xxx = (k.stroke) ? 'both' : true;

				value = x.drawHeight*(v[i][j]/m);
				drawRectBot.bind(c, [52+(one+26)*i,x.height-oldvalue-40,one,value,xxx])();
				if (j>0) {
					c.fillStyle = k.separator;
					drawRectBot.bind(c, [52+(one+26)*i,x.height-oldvalue-value-39,one,3,true])();
				}
				oldvalue += value;
			});
		});
	},
	drawLinePlot: function() {
		// Shortening variables
		var c = this.canvas;
		var a = this.data;
		var v = a.getValues();
		var k = this.style;
		var x = this;
		var m = this._max;

		// Calculating bar width
		var one = (this.drawWidth-(a.getKeys().length+1)*26)/a.getKeys().length;

		c.lineWidth = 2;
		// Drawing lines and captions
		v[0].length.times(function(j){
			if (k.autoColor) {
				if ($type(k.data)=='array') fs = k.data[0];
				else fs = k.data;
				col = new Color(fs);
				sat = col.hsb[2]+j*5;
				if (sat>100) sat=100;
				c.fillStyle = col.setBrightness(sat).hsbToRgb().rgbToHex();
			} else c.strokeStyle = k.data[j%k.data.length];
			point = [52+one/2,x.height-40-(x.drawHeight*(v[0][j]/m))];
			c.beginPath();
			c.moveTo(point[0],point[1]);

			a.getKeys().length.times(function(i){
				if (j==0) {
					el = new Element('span', {
						'styles': {
							'font-size': '11px',
							'font-weight': 'bold',
							'font-family': 'Arial, Helvetica, sans-serif',
							'color': k.captions,
							'position': 'absolute',
							'top': x.offsets.y+3+'px'
						}
					}).set('text', a.getKeys()[i]).inject($(x.cid));
					el.setStyle('left', x.offsets.x-el.getOffsetSize().x/2+52+one/2+(one+26)*i);
				}
				if (i>0) {
					point[0] += one+26;
					point[1] = x.height-40-(x.drawHeight*(v[i][j]/m));
					c.lineTo(point[0],point[1]);
				}
			});
			c.stroke();
		});

		// Drawing markers
		if (this.options.lineMarkers) {
			v[0].length.times(function(j){
				if (k.autoColor) {
					if ($type(k.data)=='array') fs = k.data[0];
					else fs = k.data;
					col = new Color(fs);
					c.fillStyle = col.setBrightness(col.hsb[2]+j*15).hsbToRgb().rgbToHex();
				} else c.fillStyle = k.data[j%k.data.length];

				a.getKeys().length.times(function(i){
					point = [52+one/2+(one+26)*i,x.height-40-(x.drawHeight*(v[i][j]/m))];
					drawRegPoly.bind(c,[point[0],point[1],6,j+3,0,true])();
				});
			});
		}
	},
	drawPie: function() {
		// Shortening variables
		var c = this.canvas,
		k = this.style,
		v = this.data.getValues(),
		a = this.data,
		x = this;

		// Converting the data type
		v.length.times(function(i) {
			if ($type(v[i])=='array') v[i] = v[i][0];
		});

		// Calculating the 100% and the center
		var sto = total(v),
		center = [26+this.drawWidth*.5, 20+this.drawHeight*.5],
		angle = 0,
		radius = (this.drawHeight<this.drawWidth) ? (this.drawHeight*0.4).floor() : (this.drawWidth*0.4).floor(),
		pStart = polarToCart(radius, angle);

		if (k.shadow) {
			c.fillStyle = k.shadowColor;
			c.beginPath();
			c.arc(center[0]+k.shadowOffset,center[1]+k.shadowOffset,radius,0,2*Math.PI,false);
			c.fill();
		}

		v.each(function(item, i) {
			curAngle = ((item/sto)*360) * (Math.PI/180);
			pEnd = polarToCart(radius, angle+curAngle);

			cap = polarToCart(1.15*radius, angle+curAngle*0.5);
			el = new Element('span', {
				'styles': {
					'font-size': '11px',
					'font-weight': 'bold',
					'font-family': 'Arial, Helvetica, sans-serif',
					'color': k.captionsPie,
					'position': 'absolute',
					'top': x.offsets.y+center[1]+cap[1]+'px'
				}
			}).set('text', a.getKeys()[i]+' ('+(item/sto*100).round()+'%)').inject($(x.cid));

			if (cap[0]+center[0] < center[0]) el.setStyle('right', 26+x.kanwas.getCoordinates().right+x.width*0.5-cap[0]+el.getOffsetSize().x);
			else el.setStyle('left', x.offsets.x+center[0]+cap[0]);

			if (k.autoColor) {
				if ($type(k.data)=='array') fs = k.data[0];
				else fs = k.data;
				col = new Color(fs);
				sat = col.hsb[2]+i*5;
				if (sat>100) sat=100;
				c.fillStyle = col.setBrightness(sat).hsbToRgb().rgbToHex();
			} else c.fillStyle = k.data[i%k.data.length];
			c.strokeStyle = k.dataBorder;
			c.lineWidth = k.borderWidth;

			// Drawing!
			c.beginPath();
			c.moveTo(pStart[0]+center[0],pStart[1]+center[1]);
			c.lineTo(center[0],center[1]);
			c.moveTo(pEnd[0]+center[0],pEnd[1]+center[1]);
			c.lineTo(center[0],center[1]);
			c.arc(center[0],center[1],radius, angle, angle+curAngle, false);
			c.fill();
			if (k.stroke) c.stroke();

			angle+=curAngle;
			pStart=pEnd;
		});
	},
	drawPie3d: function() {
		// Adding the Vector3D class
		var xd = this;

		new Asset.javascript('mae/internal/Vector3D.js', {onload:function(){

		// Shortening variables
		var c = xd.canvas,
		k = xd.style,
		v = xd.data.getValues();

		// Converting the data type
		v.length.times(function(i) {
			if ($type(v[i])=='array') v[i] = v[i][0];
		});

		// Calculating the 100% and the center
		var sto = total(v),
		center = [26+xd.drawWidth*.5, 20+xd.drawHeight*.5],
		angle = 0,
		radius = (xd.drawHeight<xd.drawWidth) ? (xd.drawHeight*0.4).floor() : (xd.drawWidth*0.4).floor(),
		pStart = polarToCart(radius, angle),
		y = 120;

		// Drawing!
		v.each(function(item, i) {
			if (k.autoColor) {
				if ($type(k.data)=='array') fs = k.data[0];
				else fs = k.data;
				col = new Color(fs);
				sat = col.hsb[2]+i*5;
				if (sat>100) sat=100;
				c.fillStyle = col.setBrightness(sat).hsbToRgb().rgbToHex();
			} else c.fillStyle = k.data[i%k.data.length];
			c.strokeStyle = k.dataBorder;
			c.lineWidth = k.borderWidth;

			curAngle = ((item/sto)*360) * (Math.PI/180);
			pEnd = polarToCart(radius, angle+curAngle);

			// 3D transformations
			vEnd = $v3(pEnd[0],y,pEnd[1]).persProjectNew();
			vStart = $v3(pStart[0],y,pStart[1]).persProjectNew();

			// Drawing!
			c.beginPath();
			c.moveTo(vStart.x+center[0],vStart.y+center[1]-y);
			c.lineTo(vEnd.x+center[0],vEnd.y+center[1]-y);
			c.lineTo(center[0],center[1]);
			//c.arc(center[0],center[1],radius, angle, angle+curAngle, false);
			c.fill();
			if (k.stroke) c.stroke();

			angle+=curAngle;
			pStart=pEnd;
		});

		}});


	},
	drawLegend: function() {
		// Shortening variables
		var n = (this.options.type.test(/^pie/)) ? this.data.getKeys() : this.options.names,
		c = this.canvas,
		v = this.data.getValues()[0],
		k = this.style,
		x = this;

		// Drawing the legend
		vlen = ($type(v)=='number') ? this.data.getValues().length : v.length;
		vlen.times(function(i){
			if (k.autoColor) {
				if ($type(k.data)=='array') fs = k.data[0];
				else fs = k.data;
				col = new Color(fs);
				sat = col.hsb[2]+i*5;
				if (sat>100) sat=100;
				c.fillStyle = col.setBrightness(sat).hsbToRgb().rgbToHex();
			} else c.fillStyle = k.data[i%k.data.length];
			drawRegPoly.bind(c, [41+(x.drawWidth-15)/vlen*i,x.height-20,15,i+3,0,true])();
			txt = (n[i]) ? n[i] : i+1;
			new Element('span', {
				'styles': {
					'font-size': '14px',
					'font-family': 'Arial, Helvetica, sans-serif',
					'color': k.legend,
					'position': 'absolute',
					'top': x.offsets.y+x.height-28
				}
			}).set('text', txt)
			.setStyle('left', x.offsets.x+61+(x.drawWidth-15)/vlen*i).inject($(x.cid));
		});
	},
	showTable: function() {
		// Shortening variables
		var n = (this.options.type.test(/^pie/)) ? this.data.getKeys() : this.options.names,
		c = this.canvas,
		a = this.data,
		v = a.getValues(),
		t = new Element('table', {
			'styles': {
				'width': this.drawWidth,
				'margin-left': this.width-this.drawWidth
			},
			'cellpadding': 0,
			'cellspacing': 0,
			'border': 0
		}).inject($(this.cid),'after').addClass('plot_table');

		// Displaying the table header
		if ($type(v[0])!='number') {
			tr = new Element('tr').inject(t).addClass('plot_header').grab(new Element('th').setHTML('&nbsp;'));
			v[0].length.times(function(i) {
				txt = (n[i]) ? n[i] : i+1;
				new Element('th').set('text', txt).inject(tr);
			});
		}

		// Displaying the data
		v.length.times(function(i){
			tr = new Element('tr').inject(t).grab(new Element('th').set('text', a.getKeys()[i]));
			v[0].length.times(function(j){
				new Element('td').set('text', v[i][j]).inject(tr);
			});
		});
	},
	update: function(url) {
		var x = this;

		new Request.JSON({
			'url': url,
			onComplete: function(resp) {
				// Converting the response
				var a = resp;
				if ($type(a)=='array') {
					var aa = new Hash();
					a.getKeys().length.times(function(i){
						if ($type(a[i])=='number') a[i] = [a[i]];
						aa.set(i, a[i]);
					});
					a = aa;
				} else if ($type(a)=='object') a = $H(a);
				x.data = a;
				x.options.names = a.getKeys();

				// Redrawing the chart
				x.redraw();
			}
		}).GET();
	},
	redraw: function() {
		// Clearing the existing chart
		this.canvas.clearRect(0,0,this.width,this.height);
		table = $$('table.plot_table');
		if (table.length>0) table.remove();
		$(this.cid).remove();

		// Injecting the captions container
		new Element('div', {'id':this.cid}).inject(this.kanwas,'after');

		// Redrawing the chart
		this.style = this.parseColors(this.options.style);
		this.draw();
	},
	parseColors: function(obj) {
		var defaults = new Hash({
			'header': '#d1e751',
			'border': '#b4b4b4',
			'guides': '#b4b4b4',
			'captions': '#b4b4b4',
			'legend': '#000000',
			'background': '#ffffff',
			'titles': '#ffffff',
			'dataBorder': '#ffffff',
			'separator': 'rgba(255,255,255,0.8)',
			'shadowColor': 'rgba(0,0,0,0.3)',
			'data': ['#4dbce9','#26ade4'],

			'lineWidth': 1,
			'guideWidth': 1,
			'borderWidth': 1.5,
			'shadowOffset': 4,

			'stroke': true,
			'shadow': true,
			'autoColor': false
		});

		if ($type(obj)=='array') {
			temp = {'data': obj};
			res = defaults.extend(temp);
		} else if ($type(obj)=='object') {
			res = defaults.extend(obj);
		} else return false;

		return res;
	}
});

// Functions to make the drawing easier
function drawRegPoly(x, y, r, num, rotation, fill) {
	var m = Math;
	var angle = (-90+rotation)*(m.PI/180);
	var pts = [];
	var dAngle = 2*m.PI/num;
	var mc = m.cos, ms = m.sin;
	this.beginPath();
	this.moveTo(r*mc(angle)+x, r*ms(angle)+y);
	while (num--) {
		angle += dAngle;
		this.lineTo(r*mc(angle)+x, r*ms(angle)+y);
	}
	if (fill==true) this.fill();
	else if (fill=='both') {
		this.fill();
		this.stroke();
	} else this.stroke();
}
function drawRectBot(x, y, w, h, fill) {
	if (fill==true) this.fillRect(x, y-h, w, h);
	else if (fill=='both') {
		this.fillRect(x, y-h, w, h);
		this.strokeRect(x, y-h, w, h);
	}
	else this.strokeRect(x, y-h, w, h);
}
function drawLine(x1, y1, x2, y2) {
	this.beginPath();
	this.moveTo(x1, y1);
	this.lineTo(x2, y2);
	this.stroke();
}
function max(array, g) {
	var last = array[0];
	array.length.times(function(i){
		if (array[i]>last) last = array[i];
	});
	mx = (last / g).ceil() * g ;
	return mx;
}
function total(array) {
	var res = 0;
	array.each(function(i){ res+=i.toInt(); });
	return res;
}
function polarToCart(r, t) {
	return [r*t.cos(), r*t.sin()];
}
